<?php

namespace App\Models;

use App\Exceptions\CouponCodeUnavailableException;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;

class CouponCode extends Model
{
    // 常量定义支持的优惠券类型
    const TYPE_FIXED = 'fixed';
    const TYPE_PERCENT = 'percent';

    // 处理中文用关联数组映射，入库则用英文
    public static $typeMap = [
        self::TYPE_FIXED => '固定金额',
        self::TYPE_PERCENT => '比例',
    ];

    protected $fillable = [
        'name',
        'code',
        'type',
        'value',
        'total',
        'used',
        'min_amount',
        'not_before',
        'not_after',
        'enabled',
    ];

    // 字段值类型转换入库
    protected $casts = [
        'enabled' => 'boolean',
    ];

    // 日期类型字段
    protected $dates = ['not_before', 'not_after'];
    // 追加序列化字段 在对模型做序列化时，会把 `$appends` (表中实际不存在的字段）中列出的访问器也序列化进来。，与toArray方法有关系
    protected $appends = ['description'];

    /**
     * 自定义生成优惠券描述信息
     *
     * @return void
     */
    public function getDescriptionAttribute()
    {
        $str = '';
        // 前置条件
        if ($this->min_amount > 0) {
            $str = '满' . str_replace('.00', '', $this->min_amount);
        }

        if ($this->type === self::TYPE_PERCENT) {
            return $str . '优惠' . str_replace('.00', '', $this->value) . '%';
        }

        return $str . '减' . str_replace('.00', '', $this->value);
    }

    /**
     * 生成可用的优惠码，防止重复
     *
     * @param integer $length
     * @return void
     */
    public static function findAvailableCode($length = 16)
    {
        do {
            $code = strtoupper(Str::random($length));

        } while (self::query()->where('code', $code)->exists());

        return $code;
    }

    /**
     * 优惠券使用条件检查
     * 与异常类render方法结合，对不同的端产生不同的输出结果
     * @param [type] $orderAmount
     * @return void
     */
    public function checkAvailable(User $user, $orderAmount = null)
    {
        // 如果优惠券没有启用，则等同于优惠券不存在
        if (!$this->enabled) {
            throw new CouponCodeUnavailableException('优惠券不存在');
        }

        if ($this->total - $this->used <= 0) {
            throw new CouponCodeUnavailableException('该优惠券已被兑完');
        }

        if ($this->not_before && $this->not_before->gt(Carbon::now())) {
            throw new CouponCodeUnavailableException('该优惠券现在还不能使用');
        }

        if ($this->not_after && $this->not_after->lt(Carbon::now())) {
            throw new CouponCodeUnavailableException('该优惠券已过期');
        }

        if (!is_null($orderAmount) && $orderAmount < $this->min_amount) {
            throw new CouponCodeUnavailableException('订单金额不满足该优惠券最低金额');
        }

        // 确保同一张优惠券，同一人只能使用一次代扣（包括退款成功的订单）
        $used = Order::where('user_id', $user->id)
            ->where('coupon_code_id', $this->id)
            ->where(function ($query) {
                $query->where(function ($query) {
                    // 订单未支付且未关闭 条件上存在优惠券id
                    $query->whereNull('paid_at')
                        ->where('closed', false);
                })->orwhere(function ($query) {
                    // 或在已支付且成功退款， 条件上用过优惠券id
                    $query->whereNotNull('paid_at')
                        ->where('refund_status', '!=', Order::REFUND_STATUS_SUCCESS);
                });
            })
            ->exists();

        if ($used) {
            throw new CouponCodeUnavailableException('你已经使用过这张优惠券了');
        }

    }

    /**
     * 计算使用优惠后的金额
     *
     * @param [type] $orderAmount
     * @return void
     */
    public function getAdjusterPrice($orderAmount)
    {
        // 固定金额
        if ($this->type === self::TYPE_FIXED) {
            // 为了保证系统健壮性，需要订单金额最少为 0.01 元,即最少得下单购买
            return max(0.01, $orderAmount- $this->value);
        }

        // 格式化折扣后的金额，number_format会做四舍五入
        return \number_format($orderAmount * (100 - $this->value) / 100, 2, '.', '');
    }

    /**
     * 使用量切换
     *
     * @param boolean $increase
     * @return void
     */
    public function changeUsed($increase = true)
    {
        if ($increase) {
            // 与检查sku库存类似，需要检查当前用量是否已经超过总量
            return $this->where('id', $this->id)->where('used', '<', $this->total)->increment('used');
        } else {
            return $this->decrement('used');
        }
    }

}
